VI.FUME
既然FUME Designer要付費才能使用,我們要在地端使用FUME就只能安裝FUME Community(FUME Engine),
https://github.com/Outburn-IL/fume-community/tree/main
將整包透過git或download下載後,在docker-compose.yml中選擇加入原先建置的HAPI FHIR services
docker-compse.yml:
version: "3.9"
services:
fume-server:
container_name: fume
build:
context: .
dockerfile: Dockerfile
ports:
- "42420:42420"
volumes:
- "./snapshots:/usr/src/app/snapshots"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "10"
compress: "true"
env_file:
- ./.env
environment:
- NODE_TLS_REJECT_UNAUTHORIZED=0
depends_on:
- fhir
db:
container_name: fhirdb
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: admin
POSTGRES_USER: admin
POSTGRES_DB: hapi
ports:
- "5432:5432"
fhir:
container_name: hapi-fhir
image: hapiproject/hapi:latest
ports:
- "8080:8080"
volumes:
- ./custom:/custom
environment:
HAPI_FHIR_USERNAME : admin
HAPI_FHIR_PASSWORD : admin
hapi.fhir.ig_runtime_upload_enabled: true
hapi.fhir.custom_content_path: /custom
profiles.active: r4
spring.datasource.url: 'jdbc:postgresql://fhirdb:5432/hapi'
spring.datasource.username: admin
spring.datasource.password: admin
spring.datasource.driverClassName: org.postgresql.Driver
spring.jpa.properties.hibernate.dialect: ca.uhn.fhir.jpa.model.dialect.HapiFhirPostgres94Dialect
spring.jpa.properties.hibernate.search.enabled: false
depends_on:
- db
(modified from hackmd.io/@hongyu0324)
接著修改.env,在fume-community-main中可以看到有兩個檔案,分別對應stateful與stateless:
.env.example.stateful
.env.example.stateless
這邊指的是FUME Engine本身有沒有要跟FHIR Server協作,來看檔案的幾個比較重要的內容:
SERVER_STATELESS=false <- true = stateless / false = stateful ,用來控制是否與FHIR Server協作
SERVER_PORT=42420 <- fume的port位址
#FHIR_SERVER_BASE=http://localhost:8080/fhir <- FHIR Server的位址,使用stateful記得要輸入,
在docker中的位址要輸入service的名稱,如
FHIR_SERVER_BASE=http://fhir:8080/fhir
FHIR_SERVER_AUTH_TYPE=BASIC <- 如果你的FHIR Server有進行權限控制,記得給FUME輸入FHIR Server的帳號
FHIR_SERVER_UN=admin
FHIR_SERVER_PW=admin
FHIR_VERSION=4.0.1 <- FHIR的版本,這裡寫4.0.1不用動
SEARCH_BUNDLE_PAGE_SIZE=20 <- Search的時候單頁的最大Resource數量
FHIR_PACKAGES=il.core.fhir.r4@0.11.0,hl7.fhir.us.core <- 匯入IG,筆者沒有使用過但推測這個功能僅是將profile轉換為template使用
該專案的docs/getting-started.md還有另一條選擇可以使用,透過nodejs內嵌的方式執行轉換,但筆者實際測試不能使用太大的內容,所以並未使用這個方法。
在挑選完要使用stateless / stateful後,接下來就是cd到fume-community-main執行docker compose
cd YOURDIRECTORY/fume-community-main
docker compose up
這邊要注意一點是,透過compose啟動整個服務,會將三個(hapi fhir, fume, db)一起啟動,但這會有一個小問題
正常來說,HAPI FHIR Server的啟動時間會遠大於FUME,這也導致了當FUME已經啟動完成時,FUME還不能抓取到FHIR Server的部署位置,
因此又會跑回stateless的模式運作,由於HAPI FHIR並沒有一個可以記錄health check的方式,docker無法知道HAPI FHIR的確切啟動完成時間,
stateful暫時的解法有兩個,第一個是Compose去耦合,把HAPI FHIR與Fume分別以兩個服務啟動,或是等到HAPI FHIR完全啟動後,再啟動/重啟FUME,
FUME Community若以stateful啟動,在啟動時會去向FHIR Server請求StructureMap與ConceptMap等Resource,作為用來轉換的預先載入資源。
如果是stateless的話,FUME就只會單純啟動,無論使用哪種方式,要確認啟動的最好方式就是
GET | http://localhost:42420
如果FUME是正常啟動的話應該會丟回FUME的版本,
{
"fume_version": "FUME Community v2.18.5"
}
到這一步FUME就算是正式啟動完成了,那下一步呢?
前面我們提到過,FUME的組成由輸入的JSON資料與FUME Mapping所構成,
在REST的架構上的輸入方法在這一步會因為選擇的模式而有區別
--
首先是stateful,因為輸入的方式會稍微簡單一些,但兩個模式要做的事情是很類似的
在FUME官方的說明中,在FUME Designer Playground中儲存FUME Mapping,自訂名稱。
接著在右上方的FUME欄位中 輸入
$resolve("StructureMap/剛才的儲存名稱")
右下方的Resource就會返回一個StructureMap,把這個Resource上傳到本地的FHIR Server,並且重啟FUME Engine
可以在FUME的啟動過程中觀測到FUME有從FHIR Server拿到這個Resource,當FUME重新啟動完成後,
在POSTMAN中執行
POST | http://localhost:42420/Mapping/剛才的StructureMap的ResourceID
Headers : Content-Type -> application/json
Body : raw -> JSON -> 把原先在FUME Designer中左側的那一整包輸入的資料貼上去按下Send送出,
下方的Response Body即為轉換完成的FHIR結果。
--
stateless的方法也很簡單,因為FUME此時沒有FHIR Server能夠借Resource了,在輸入部分,要改成
POST | http://localhost:42420/
Headers : Content-Type -> application/json
Body : raw -> JSON ->
{
"input" : "",
"fume" : ""
}
其中input為輸入的JSON,就是FUME Designer中左側那一整包輸入資料;
最快的方式就是把透過$resolve("StructureMap/剛才的儲存名稱")得到的StructureMap中的
group.rule.extension.valueExpression.expression欄位中複製出來貼到body.fume裡面
下方的Response Body即為轉換完成的FHIR結果。
這兩個方式看起來都不錯,但有一個環節會出問題,
無論透過哪個方式,都免不了要將自己撰寫的FUME Mapping存檔在Playground中,再用$resolve抽取出來StructureMap,
這樣會造成資料外洩的問題,有沒有方法能夠將這一步取代掉的,當然有,
最簡單暴力的方法就是自己刻一個適用於FUME的StructureMap,
然後將FUME Mapping字串化後放入group.rule.extension.valueExpression.expression,
再上傳FHIR Server讓FUME來讀取
又或者是stateless直接將自己的FUME Mapping字串化塞進body.fume中,
總之,這個部分可以自己透過程式來整理完成,實現方法諸多在這裡就不提供
明天開始來講要怎麼寫FUME Mapping囉,今天寫的篇幅偏長,但內容的確是這麼多